﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.DataTypes;

namespace SSharp.Core.Evaluator {
	/// <summary>
	/// The environment is a set of frames, each containing a dictionary for variable = value.
	/// </summary>
	public interface IEnvironment : IEnumerable<IFrame> {
		/// <summary>
		/// Returns an environment with the given frame as the first frame, and this environment as the rest of the frames.
		/// </summary>
		IEnvironment AddFrame(IFrame frame);

		/// <summary>
		/// Returns the parent environment
		/// </summary>
		IEnvironment GetParent();

		/// <summary>
		/// Returns the top frame of the environment
		/// </summary>
		IFrame GetTopFrame();

		/// <summary>
		/// Looks up the given key in all the frames, from top to bottom.
		/// Cannot be used for assignment; you'd have to use a specific frame for that.
		/// </summary>
		object this[Symbol key] { get; }
	}

	/// <summary>
	/// Implements the IEnvironment interface
	/// </summary>
	public class Environment : IEnvironment {
		private IEnvironment parent;
		private IFrame frame;

		/// <summary>
		/// Constructs an empty environment
		/// </summary>
		public Environment() {
			parent = null;
			frame = new Frame();
		}

		/// <summary>
		/// Constructs an environment with the given parent environment and frame
		/// </summary>
		private Environment(IEnvironment parent, IFrame frame) {
			this.parent = parent;
			this.frame = frame;
		}

		/// <summary>
		/// Returns an environment with the given frame as the first frame, and this environment as the rest of the frames.
		/// </summary>
		public IEnvironment AddFrame(IFrame frame) {
			return new Environment(this, frame);
		}

		/// <summary>
		/// Returns the parent environment
		/// </summary>
		public IEnvironment GetParent() {
			return parent;
		}

		/// <summary>
		/// Returns the top frame of the environment
		/// </summary>
		public IFrame GetTopFrame() {
			return frame;
		}

		public object this[Symbol key] {
			get {
				foreach (IFrame frame in this) {
					Box value = frame[key];
					if (value != null) {
						return value.Value;
					}
				}
				throw new UnboundedVariableException("Unbounded variable: " + key);
			}
		}

		/// <summary>
		/// Enumerates over the frames of this environment.
		/// </summary>
		public IEnumerator<IFrame> GetEnumerator() {
			// we go through each parent environment to return its frame.
			IEnvironment env = this;
			while (env != null) {
				yield return env.GetTopFrame();
				env = env.GetParent();
			}
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}
	}
}
